<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="../libs/style.css" />
    <title>发布订阅者模式</title>
</head>

<body>
    <div class="container">
        <article>
            <h2>发布订阅模式：</h2>
            <p>大概很多人都和我一样，觉得发布订阅模式里的Publisher，就是观察者模式里的Subject，而Subscriber，就是Observer。Publisher变化时，就主动去通知Subscriber。</p>
            <p>其实并不是。</p>
            <h3>在发布订阅模式里，发布者，并不会直接通知订阅者，换句话说，发布者和订阅者，彼此互不相识。</h3>
            <p>互不相识？那他们之间如何交流？</p>
            <h3>答案是，通过第三者，也就是在消息队列里面，我们常说的经纪人Broker。</h3>

        </article>
        <img src="./image/publisher-subscriber.jpg" alt="">
        <article>
            <p>发布者只需告诉Broker，我要发的消息，topic是AAA；</p>

            <p>订阅者只需告诉Broker，我要订阅topic是AAA的消息；</p>

            <p>于是，当Broker收到发布者发过来消息，并且topic是AAA时，就会把消息推送给订阅了topic是AAA的订阅者。当然也有可能是订阅者自己过来拉取，看具体实现。</p>

            <strong>也就是说，发布订阅模式里，发布者和订阅者，不是松耦合，而是完全解耦的。</strong>

            <p>放一张极简的图，给大家对比一下这两个模式的区别：</p>
        </article>
        <h2>总结</h2>
        <h3>1、从表面上看：</h3>
        <ul>
            <li>观察者模式里，只有两个角色 —— 观察者 + 被观察者</li>
            <li>而发布订阅模式里，却不仅仅只有发布者和订阅者两个角色，还有一个经常被我们忽略的 —— 经纪人Broker</li>
        </ul>

        <h3>2、往更深层次讲：</h3>
        <ul>
            <li>观察者和被观察者，是松耦合的关系</li>
            <li>发布者和订阅者，则完全不存在耦合</li>
        </ul>

        <h3>3、从使用层面上讲：</h3>
        <ul>
            <li>观察者模式，多用于单个应用内部</li>
            <li>发布订阅模式，则更多的是一种跨应用的模式(cross-application pattern)，比如我们常用的消息中间件</li>
        </ul>
    </div>
    <script type="text/javascript">
        /**
         * @description: 经纪人：控制中心相当于订阅器
         * @param {*}
         * @return {*}
         */
        function Broker() {}
        const getBroker = (function(){
            let broker = null
            return function(){
                if (!broker) {
                    broker = new Broker()
                }
                return broker;
            }
        })()
        Broker.prototype = {
            /**
             * 订阅话题
             */
            topics: [],
            /**
             * 订阅人
             */
            subscribers: [],
            /**
             * @description: 订阅
             * @param {*} topic
             * @return {*}
             */
            subscribe: function (subscriber) {
                const {topic,user} = subscriber
                if (!this.topics.includes(topic)) {
                    this.topics.push(topic)
                    this.subscribers.push({topic: topic,users: [user]})
                } else {
                    const index = this.topics.indexOf(topic)
                    this.subscribers[index].users.push(user)
                }
            },
            /**
             * @description:取消订阅
             * @param {}
             * @return {*}
             */
            unsubscribe: function (subscriber) {
                const { topic, user } = subscriber
                if (!this.topics.includes(topic)) {
                    this.topics.push(topic)
                    this.subscribers.push({ topic: topic, users: [user] })
                } else {
                    const index = this.topics.indexOf(topic)
                    this.subscribers[index].users.push(user)
                }
            },
            /**
             * @description: 发布/通知消息
             * @param { string } topic
             * @return { Object } data
             */
            notify: function (topic, data) {
                const item = this.subscribers.filter(item => item.topic === topic)
                if(item.length>0){
                    const {users} = item[0]
                    users.forEach(element => {
                        element.receive(topic,data)
                    });
                }
            }
        }
        /**
         * @description: 订阅者
         * @param  {*}
         * @return {*}
         */
        function Subscriber(options) {
            const {name,topic} = options
            this.name = name
            // this.topic = topic
            const broker = getBroker()
            if (topic) {
                broker.subscribe({user: this,topic: topic})
            }
        }
        Subscriber.prototype = {
            name: String, //姓名
            // topic: String, //话题
            /**
             * @description: 接收订阅
             * @description: 订阅
             * @param {*} topic
             * @param {*} obj
             * @return {*}
             */
            receive: function (topic,data) {
                console.log(`${this.name}：接收了订阅的  ${topic} 话题消息：${JSON.stringify(data).toString()}`)
            }
        }
        const sub1 = new Subscriber({
            name: "小名",
            topic: "topic1"
        })
        const sub2 = new Subscriber({
            name: "小美",
            topic: "topic2"
        })
        const sub3 = new Subscriber({
            name: "小花",
            topic: "topic1"
        })
        const sub4 = new Subscriber({
            name: "小费",
            topic: "topic1"
        })

        /**
         * @description: 发布者
         * @param  {*}
         * @return {*}
         */
        function Publisher() {
            this.broker = getBroker()
        }
        Publisher.prototype = {
            broker: Object,
            publish: function (topic, data) {
                this.broker.notify(topic, data)
            }
        }

        const publisher = new Publisher()
        publisher.publish("topic2", {
            message: "后天开始去杭州。"
        })
        publisher.publish("topic1", {
            message: "明天开始去无锡。"
        })
        publisher.publish("topic3", {
            message: "后天开始去苏州。"
        })

    </script>
</body>

</html>
